iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0
Software Development

以MicroPython在ESP32上實作Insulin Delivery Service系列 第 4

Day 04 - 整體架構 & logger & 字串拼接效能

  • 分享至 

  • xImage
  •  

各位看官~今天咱們來進行第一次的重構吧~

1. 整體架構

為了更好管理不同功能的開發,我們將 IDS server 分為幾個模組:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799tQMpjedJ8G.png

目錄名稱 功能
ble 藍牙相關
common 所有模組都可能用到的共通功能,如 log 的輸出、SFLOAT 的轉換等
core 核心功能,如胰島素注射、歷史紀錄的儲存等

而根目錄只包含 main.py 和設定相關的程式和檔案。

雖然 ESP32 支援多執行緒,但為了節省記憶體,以及簡化設計,本系統只會使用主執行緒和中斷處理程序。

接著,咱們來製作所有模組都需要的 log 輸出功能吧~

2. common/logger.py

此檔案只有一個公開方法 write():

def write(msg: str):
    """不建議在 ISR 內呼叫。"""

    now = machine.RTC().datetime()
    print(
        f"{now[0]}-{now[1]:02d}-{now[2]:02d} "
        f"{now[4]:02d}:{now[5]:02d}:{now[6]:02d}.{now[7]:06d}  "
        f"{msg}"
    )

根據 MicroPython 的限制,不應該在 ISR 內由 heap 配置記憶體,而字串拼接,尤其格式化,通常需要配置記憶體來處理,因此不建議在 ISR 內使用。

一開始咱們用 machine.RTC() 來得到 ESP32 的 RTC,然後以其 datetime() 來取得目前的日期時間,其回傳格式是 tuple:

索引 意義
0
1
2
3 星期,0 ~ 6,星期一為 0
4
5
6
7 毫秒

這裡有個要注意的點,雖然根據文件描述,machine.RTC() 似乎會創立一個新的 RTC 實例,但在咱們的環境下,無論呼叫幾次,都會得到同一個實例(以 id() 來驗證)。

3. 字串拼接

在 write() 裡,因為需要格式化,所以使用 f-string 來達成,當然也可以用 str.format() 或 %-formatting。這裡咱們不考慮複雜的使用情境,只探討最簡單的字串拼接:

def test_f_str(n, s):
    for i in range(times):
        t = f"arg ({n}, {s})"


def test_concate(n, s):
    for i in range(times):
        t = "arg (" + str(n) + ", " + s + ")"


def test_join(n, s):
    for i in range(times):
        t = "".join(("arg (", str(n), ", ", s, ")"))


def test_percent(n, s):
    for i in range(times):
        t = "".join("arg (%d, %s)" % (n, s))

執行 3 輪,每輪各執行 10000 次:
第 1 輪:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799InvPPBybrM.png

第 2 輪:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799DABLyeHh2L.png

第 3 輪:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799yL3xwqrAkA.png

撇除 %-formatting,可以發現使用 join() 是執行最快,但記憶體使用量卻可能最多;而 f-string 耗費的記憶體最少。

不過這只是一種情境,若將程式改為(重複使用 s):

def test_f_str(n, s):
    for i in range(times):
        s = f"arg ({n}, {s})"


def test_concate(n, s):
    for i in range(times):
        s = "arg (" + str(n) + ", " + s + ")"


def test_join(n, s):
    for i in range(times):
        s = "".join(("arg (", str(n), ", ", s, ")"))


def test_percent(n, s):
    for i in range(times):
        s = "".join("arg (%d, %s)" % (n, s))

執行 3 輪,每輪各執行 100 次(太多次會導致 MemoryError):
第 1 輪:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799gSzQbMihN8.png

第 2 輪:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799dpssrzfOKf.png

第 3 輪:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799djSOZNzV8q.png

join() 還是最快,但記憶體使用量也是最少。

不過這種測試只能做為一種參考,畢竟有些參數不同,結果可能就有變化。而且在輸出 log 時,需要拼接的字串不至於太長,耗費的時間和記憶體應該有限。

4. 執行

在執行時,注意要將 common 這個資料夾先上傳到板子上,否則執行 main.py 時會出現找不到 common 模組的錯誤:
https://ithelp.ithome.com.tw/upload/images/20250804/20177799qTElV1CBdb.png

main.py 可以不用上傳,只要在 Thonny 開啟並執行就好。若 main.py 也上傳到板子,那以後板子通電後,就會直接執行 main.py,這在開發階段會有一些不方便。


上一篇
Day 03 - ISR 與 micropython.schedule 的限制
下一篇
Day 05 - 製作外部設定檔
系列文
以MicroPython在ESP32上實作Insulin Delivery Service31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言